﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Work.ProtoclBase;

namespace System.Work.ClientEngine.FPXH
{
    public class FPXHClient : IFPXHClient
    {
        #region 变量

        private string _localIP = "127.0.0.1";
        private int _localPort = 9998;
        private string _remoteIP = "127.0.0.1";
        private int _remotePort = 9999;
        private SocketClientSync<BufferedPackageInfo> _client;
        private byte _slaveID;
        private Dictionary<string, IFPXHCommand> _Commands = null;
        private ConcurrentQueue<FPXHCommandPrefix> _BuffCommandsData;
        private ConcurrentQueue<byte[]> _BuffSendReadData = null;
        private ConcurrentQueue<byte[]> _BuffSendWriteData = null;
        private List<CommandFormat> _formats = null;
        private AutoResetEvent _sync = new AutoResetEvent(false);
        private readonly int _timeout = 2000;//2s超时时间

        #endregion
        #region 属性
        public bool IsConnected
        {
            get;
            private set;
        }
        #endregion
        #region 事件
        public event EventHandler<ReadDataEventArgs> OnReadCallback;
        public event EventHandler<WriteDataEventArgs> OnWriteCallback;
        public event EventHandler<ErrorDataEventArgs> OnErrorCallback;
        public event EventHandler OnClientClosed;
        public event EventHandler OnClientConnected;
        public event EventHandler OnClientError;
        #endregion
        #region 构造函数
        public FPXHClient(string localIP, int localPort, string remoteIP, int remotePort, byte slaveID = 1)
        {
            _localIP = localIP;
            _localPort = localPort;
            _remoteIP = remoteIP;
            _remotePort = remotePort;
            _slaveID = slaveID;
            _BuffSendReadData = new ConcurrentQueue<byte[]>();
            _BuffSendWriteData = new ConcurrentQueue<byte[]>();
            var readCommand = new FPXHReadCommand();
            var writeCommand = new FPXHWriteCommand();
            var errorCommand = new FPXHErrorCommand();
            _Commands = new Dictionary<string, IFPXHCommand>
            {
                { readCommand.Name, readCommand },
                { writeCommand.Name, writeCommand },
                { errorCommand.Name, errorCommand }
            };

            _formats = new List<CommandFormat>()
            {
                new AddressRformat("R"),
                new AddressDformat("D")
            };

            Thread t = new Thread(new ThreadStart(SendProcess)) { IsBackground = true, Name = "FPXH发送信号线程" };
            t.Start();
        }


        #endregion
        #region 自定义方法

        #region tcp clint events
        private void _client_NewPackageReceived(object sender, PackageEventArgs<BufferedPackageInfo> e)
        {
            FPXHCommandPrefix prefix;
            _sync.Set();
            if (_BuffCommandsData.TryDequeue(out prefix))
            {
                if (_Commands[prefix.CommandCode] is FPXHReadCommand)
                    ((FPXHReadCommand)_Commands[prefix.CommandCode]).ExecuteCommand(OnReadCallback, e.Package, prefix);
                else if (_Commands[prefix.CommandCode] is FPXHWriteCommand)
                    ((FPXHWriteCommand)_Commands[prefix.CommandCode]).ExecuteCommand(OnWriteCallback, e.Package, prefix);
                else if (_Commands[prefix.CommandCode] is FPXHErrorCommand)
                    ((FPXHErrorCommand)_Commands[prefix.CommandCode]).ExecuteCommand(OnErrorCallback, e.Package, prefix);
                else
                    Debug.WriteLine("error:Unknown type.");
            }
            else
            {
                Debug.WriteLine("error:_Client_NewPackageReceived _BuffCommandsData dequeue failed.");
            }
        }

        private void _client_Error(object sender, ErrorEventArgs e)
        {
            if (OnClientError != null)
                OnClientError(sender, e);
        }

        private void _client_Connected(object sender, EventArgs e)
        {
            IsConnected = true;
            if (OnClientConnected != null)
                OnClientConnected(sender, e);
        }

        private void _client_Closed(object sender, EventArgs e)
        {
            if (OnClientClosed != null)
                OnClientClosed(sender, e);
        }
        #endregion

        #region Send bytes

        private void SendProcess()
        {
            while (true)
            {
                try
                {
                    if (_client == null || !IsConnected)
                    {
                        Thread.Sleep(100);
                        continue;
                    }
                    byte[] data = null;
                    if(_BuffSendWriteData.TryDequeue(out data))
                    {
                        _client.Send(data);
                        if (!_sync.WaitOne(_timeout))
                        {//命令超时-出队当前命令-外调时按超时处理
                            FPXHCommandPrefix prefix;
                            _BuffCommandsData.TryDequeue(out prefix);
                        }
                    }
                    data = null;
                    if (_BuffSendReadData.TryDequeue(out data))
                    {
                        _client.Send(data);
                        if (!_sync.WaitOne(_timeout))
                        {//命令超时-出队当前命令-外调时按超时处理
                            FPXHCommandPrefix prefix;
                            _BuffCommandsData.TryDequeue(out prefix);
                        }
                    }
                }
                catch (Exception ex)
                {
                    OnErrorCallback(this, new ErrorDataEventArgs() { ErrorCode = "SendError", ErrorMsg = ex.ToString() });
                }
            }
        }

        private void SendRead(byte[] data)
        {
            _BuffSendReadData.Enqueue(data);
        }
        private void SendWrite(byte[] data)
        {
            _BuffSendWriteData.Enqueue(data);
        }

        #endregion        

        #endregion
        #region 接口方法
        public bool Connect()
        {
            bool bResult = true;
            try
            {
                Disconnect();
                _client = new SocketClientSync<BufferedPackageInfo>();
                _client.Closed += _client_Closed;
                _client.Connected += _client_Connected;
                _client.Error += _client_Error;
                _client.NewPackageReceived += _client_NewPackageReceived;
                _client.LocalEndPoint = new IPEndPoint(IPAddress.Parse(_localIP), _localPort);
                var filter = new FPXHFilter(new byte[] { 0x0D });
                _client.Initialize(filter);
                var result = _client.ConnectAsync(new IPEndPoint(IPAddress.Parse(_remoteIP), _remotePort));
                _BuffCommandsData = new ConcurrentQueue<FPXHCommandPrefix>();
            }
            catch (Exception)
            {
                bResult = false;
                IsConnected = false;
                throw;
            }
            return bResult;
        }

        public bool Disconnect()
        {
            bool bResult = true;
            try
            {
                IsConnected = false;
                if (_client != null)
                {
                    if (_BuffCommandsData != null)
                    {
                        FPXHCommandPrefix cmd = null;
                        while (_BuffCommandsData.TryDequeue(out cmd))
                        {
                            //TODO:
                        }
                        _BuffCommandsData = null;
                    }
                    _client.Closed -= _client_Closed;
                    _client.Connected -= _client_Connected;
                    _client.Error -= _client_Error;
                    _client.NewPackageReceived -= _client_NewPackageReceived;
                    var result = _client.Close();
                    _client = null;
                }
            }
            catch (Exception)
            {
                bResult = false;
                throw;
            }
            return bResult;
        }

        public void Dispose()
        {
            try
            {
                Disconnect();
            }
            catch (Exception)
            {
                throw;
            }
        }

        public bool Read(string address)
        {
            bool bResult = true;
            try
            {
                Read(address, address, 1);
            }
            catch (Exception)
            {
                bResult = false;
                throw;
            }
            return bResult;
        }

        public bool Read(string startAdd, string endAdd, int count)
        {
            bool bResult = true;
            try
            {
                var format = _formats.FirstOrDefault(x => x.AddressCode.Equals(startAdd.Substring(0, 1)));
                if (format == null)
                    throw new Exception(string.Format("尚未添加{0}地址类型格式", startAdd.Substring(0, 1)));
                byte[] bytes = format.MakeReadPacket(new string[] { startAdd, endAdd }, new int[] { count });
                _BuffCommandsData.Enqueue(new FPXHCommandPrefix("r", new string[] { startAdd, endAdd }) { Count = count, CurFormat = format });
                SendRead(bytes);
            }
            catch (Exception)
            {
                bResult = false;
                throw;
            }
            return bResult;
        }

        public bool Write(string address, int value)
        {
            bool bResult = true;
            try
            {
                Write(address, address, 1, new int[] { value });
            }
            catch (Exception)
            {
                bResult = false;
                throw;
            }
            return bResult;
        }

        public bool Write(string startAdd, string endAdd, int count, int[] values)
        {
            bool bResult = true;
            try
            {
                var format = _formats.FirstOrDefault(x => x.AddressCode.Equals(startAdd.Substring(0, 1)));
                if (format == null)
                    throw new Exception(string.Format("尚未添加{0}地址类型格式", startAdd.Substring(0, 1)));
                byte[] bytes = format.MakeWritePacket(new string[] { startAdd, endAdd }, values);
                _BuffCommandsData.Enqueue(new FPXHCommandPrefix("w", new string[] { startAdd, endAdd }) { Count = count, CurFormat = format });
                SendWrite(bytes);
            }
            catch (Exception)
            {
                bResult = false;
                throw;
            }
            return bResult;
        }
        #endregion
    }
}
